package edu.study.ops.service;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import edu.study.api.common.dto.RespDto;
import edu.study.api.common.json.JsonUtils;
import edu.study.common.util.DateUtils;
import edu.study.common.util.cache.Cache;
import edu.study.ops.config.OpsConstant;
import edu.study.ops.dto.PartETagDto;
import edu.study.ops.dto.UploadSplitFileBeforeDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

@Service
@Slf4j
public class OssService {

    @Autowired
    private OpsConstant opsConstant;

    @Autowired
    private Cache cache;

    private OSSClient ossClient;

    @PostConstruct
    void init() {
        ossClient = new OSSClient(opsConstant.getOssEndpoint(), opsConstant.getOssAccessId(), opsConstant.getOssAccessKey());
    }

    public String uploadFile(InputStream inputStream, String fileName) {
        // 上传文件流
        ossClient.putObject(opsConstant.getOssBucketName(), fileName, inputStream);
        return opsConstant.getOssUrlPrefix() + fileName;
    }

    public String uploadFileWithTimeStamp(InputStream inputStream, String fileName) {
        // 上传文件流
        int pos = fileName.lastIndexOf('.');
        String fileNameStamp = new StringBuffer(DateUtils.toShortDateString2(DateUtils.getNow())).append("/").append(fileName.substring(0, pos)).append("_").append(System.currentTimeMillis()).append(fileName.substring(pos)).toString();
        ossClient.putObject(opsConstant.getOssBucketName(), fileNameStamp, inputStream);
        return opsConstant.getOssUrlPrefix() + fileNameStamp;
    }

    public String uploadFileWithSplit(MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        log.info("OssService.uploadFileWithSplit, fileName={}", fileName);
        int pos = fileName.lastIndexOf('.');
        String objectName = new StringBuffer(DateUtils.toShortDateString2(DateUtils.getNow())).append("/").append(fileName.substring(0, pos)).append("_").append(System.currentTimeMillis()).append(fileName.substring(pos)).toString();

        // 计算文件有多少个分片。
        final long partSize = opsConstant.getUploadFileSize() * 1024 * 1024L; // 20MB
        long fileLength = file.getSize();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }

        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(opsConstant.getOssBucketName(), objectName);
        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
        // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
        String uploadId = result.getUploadId();
        log.info("OssService.uploadFileWithSplit, uploadId={}", uploadId);

        final int fianlPartCount = partCount;
        // 遍历分片上传。
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
            InputStream instream1 = file.getInputStream();
            // 跳过已经上传的分片。
            instream1.skip(startPos);
            final int ii = i + 1;
            new Thread(new Runnable() {
                public void run() {
                    try {
                        uploadSplitFile(instream1, curPartSize, ii, fianlPartCount, objectName, uploadId);
                    } catch (IOException e) {
                        log.error("OssService.uploadFileWithSplit, e={}", e);
                    }
                }
            }).start();
        }
        return opsConstant.getOssUrlPrefix() + objectName;
    }

    public RespDto<UploadSplitFileBeforeDto> uploadSplitFileBefore(String fileName) {
        log.info(">>>LoanTransService.uploadSplitFileBefore begin, objectName={}", fileName);
        RespDto<UploadSplitFileBeforeDto> resp = new RespDto<UploadSplitFileBeforeDto>();

        int pos = fileName.lastIndexOf('.');
        String objectName = new StringBuffer(DateUtils.toShortDateString2(DateUtils.getNow())).append("/").append(fileName.substring(0, pos)).append("_").append(System.currentTimeMillis()).append(fileName.substring(pos)).toString();
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(opsConstant.getOssBucketName(), objectName);
        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
        // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
        String uploadId = result.getUploadId();
        UploadSplitFileBeforeDto uploadSplitFileBeforeDto = new UploadSplitFileBeforeDto();
        uploadSplitFileBeforeDto.setFileName(objectName);
        uploadSplitFileBeforeDto.setUploadId(uploadId);
        resp.setData(uploadSplitFileBeforeDto);

        log.info("<<<LoanTransService.uploadSplitFileBefore end, resp={}", resp);
        return resp;
    }

    public String uploadSplitFile(InputStream instream, long partSize, int partNo, int partCount, String objectName, String uploadId) throws IOException {
        log.info("OssService.uploadSplitFile, objectName={}..., {}/{}", objectName, partNo, partCount);
        String bucketName = opsConstant.getOssBucketName();

        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setKey(objectName);
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setInputStream(instream);
        // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
        uploadPartRequest.setPartSize(partSize);
        // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
        uploadPartRequest.setPartNumber(partNo);
        // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
        // 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。

        String jsonKey = new StringBuffer(objectName).append(":").append(partCount).toString();
        String jsonStr = JsonUtils.toJson(uploadPartResult.getPartETag());
        String[] strs = new String[]{jsonStr};
        Long count = cache.rpush(jsonKey, strs);
        if (count.intValue() == partCount) {
            List<String> strList = cache.lrange(jsonKey, 0, count.intValue() - 1);
            if (!CollectionUtils.isEmpty(strList)) {
                List<PartETag> partETagList = new ArrayList<PartETag>();
                for (int i = 0; i < strList.size(); i++) {
                    String str = strList.get(i);
                    log.info("OssService.uploadSplitFile, str={}", str);
                    PartETagDto partETagDto = JsonUtils.fromJson(str, PartETagDto.class);
                    PartETag partETag = new PartETag(partETagDto.getPartNumber(), partETagDto.getETag(), partETagDto.getPartSize(), partETagDto.getPartCRC());
                    partETagList.add(partETag);
                }
                // 排序。partETags必须按分片号升序排列。
                Collections.sort(partETagList, new Comparator<PartETag>() {
                    public int compare(PartETag p1, PartETag p2) {
                        return p1.getPartNumber() - p2.getPartNumber();
                    }
                });
                log.info("OssService.uploadSplitFile, partETagList={} end", JsonUtils.toJson(partETagList));
                // 在执行该操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
                CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETagList);
                ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            }
        }
        //关闭OSSClient。
        //ossClient.shutdown();
        return objectName;
    }

    public RespDto<byte[]> downFile(String path) {
        log.info("OssService.downFile begin path={}", path);
        RespDto<byte[]> resp = new RespDto<byte[]>();
        int i = path.indexOf(".aliyuncs.com/");
        if (i == -1) {
            return resp;
        }
//		try {
//			path = new String(path.getBytes("ISO-8859-1"), "UTF-8");
//		} catch (UnsupportedEncodingException e1) {
//			log.error("OssService.downFile exception={}", e1);
//		}
        OSSObject oSSObject = ossClient.getObject(opsConstant.getOssBucketName(), path.substring(i + 14, path.length()));
        byte[] fileByte = null;
        try {
            fileByte = this.read(oSSObject.getObjectContent());
        } catch (IOException e) {
            log.error("OssService.downFile exception={}", e);
        }
        resp.setData(fileByte);
        return resp;
    }

    private byte[] read(InputStream inputStream) throws IOException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int num = inputStream.read(buffer);
            while (num != -1) {
                baos.write(buffer, 0, num);
                num = inputStream.read(buffer);
            }
            baos.flush();
            return baos.toByteArray();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}
